package com.kob.backend.utils.kkt;

import java.util.Random;

/**
 * <pre>{@code
 * java 实现IDA
 * }</pre>
 *
 * @author Summerday
 * @date 2023/3/5
 */
public class Market {

    double[] theta;

    double[][][] mu, u, v, x, y;
    double[][] lambda, temp, utility1, utility2, optbid1, optbid2;


    public Market(int m, int n) {
        this.M = m;
        this.N = n;

        /**
         * 记录每一轮迭代的mu值, 比如mu[t] 表示第t轮的mu值矩阵
         */
        mu = new double[T][M + 1][N + 1];

        /**
         * 记录每一轮迭代的lambda值, ，比如lambda[t] 表示第t轮的lambda值数组
         */
        lambda = new double[T][N + 1];

        /**
         *
         * θi ∈ (0, 1) 是 EV i 的位置相关因子
         * 电动汽车在执行数据收集任务时，会消耗自身的电量和计算资源。
         * 我们认为成本不仅与电动汽车收集的数据量成正比，而且与电动汽车所在位置的重要性相关。
         * EV i 的位置越远，收集数据所需的功耗就越大
         *
         */
        theta = new double[N + 1];

        /**
         * 每个 RSU j ∈ M 为每个 EV i ∈ N 提交一个投标 u（j,i） ≥ 0，代表要支付的能源数量。
         */
        u = new double[T][M + 1][N + 1];

        /**
         * 每个 EV i ∈ N 为每个 RSU j ∈ M 提交出价 v（i,j） ≥ 0，代表要出售的数据量。
         */
        v = new double[T][N + 1][M + 1];

        /**
         * 令 xj,i 表示 RSU j 期望从 EVi 接收的数据百分比，
         *
         */
        x = new double[T][M + 1][N + 1];

        /**
         *  yi,j 表示 EV i 提供给 RSU j 的实际数据百分比。
         */
        y = new double[T][N + 1][M + 1];

        temp = new double[T][N + 1];

        utility1 = new double[M + 1][N + 1];
        optbid1 = new double[M + 1][N + 1];

        utility2 = new double[N + 1][M + 1];
        optbid2 = new double[N + 1][M + 1];
    }

    /**
     * RSU [1,2,3...M]
     */
    int M;

    /**
     * EV [1,2,3...N]
     */
    int N;

    /**
     * 预设值迭代次数
     */
    static final int T = 100;

    static final int LEN = 300000;

    /**
     * 判定收敛的阈值
     */
    static final double EP1 = 0.05;
    static final double EP2 = 0.05;

    /**
     * 步长
     */
    static final double s = 0.03;

    /**
     * 迭代 轮次
     */
    int t = 1;

    static Random random = new Random();


    /**
     * 模拟IDA迭代双边拍卖算法
     */
    public void IDA() {
        init_params();
        boolean flag = true;
        while(flag) {
            t += 1;
            calU(u);
            calV(v);
            calXY(u, v, x, y);
            getSum(y, temp);
            updateParams(temp, x, y);

            System.out.printf("【Round %d】QEvI\n", t);
            MatrixUtils.show(getQEvI(mu, lambda, v), N, M);
            //print(t - 1);

            if(t + 1 == T || checkRes(u, v)) {
                System.out.println("【final】本次迭代 共迭代" + t + "轮");
                System.out.printf("【final】第%d轮迭代结果\n", t);
                System.out.println("【final】U");
                MatrixUtils.show(u[t], M, N);
                System.out.println("【final】X");
                MatrixUtils.show(x[t], M, N);
                System.out.println("【final】V");
                MatrixUtils.show(v[t], N, M);
                System.out.println("【final】Y");
                MatrixUtils.show(y[t], N, M);
                System.out.println("【final】PRsuJ");
                MatrixUtils.show(getPRsuJ(u), M, N);
                System.out.println("【final】QEvI");
                MatrixUtils.show(getQEvI(mu, lambda, v), N, M);
                flag = false;
            }
        }
    }

    /**
     * 初始化lambda和mu矩阵，这部分信息需要运营商公布给RSU和EV
     */
    void init_params() {
        double[][] m = mu[t];
        for(int i = 1; i <= M; i++) {
            for(int j = 1; j <= N; j++) {
                m[i][j] = random.nextDouble();
            }
        }

        //mu[t] = new double[][]{
        //        {0, 0, 0, 0, 0},
        //        {0, 0.3517, 0.5853, 0.9172, 0.7572},
        //        {0, 0.3804, 0.0759, 0.5308, 0.9340},
        //        {0, 0.5688, 0.0119, 0.1622, 0.3112},
        //        {0, 0.1656, 0.2630, 0.6892, 0.4505},
        //        {0, 0.2290, 0.1524, 0.5383, 0.0782}};


        double[] lam = lambda[t];
        for(int i = 1; i <= N; i++) {
            lam[i] = random.nextDouble();
        }
        //lambda[t] = new double[]{0.0, 0.913337361501670,
        //        0.825816977489547,
        //        0.996134716626886,
        //        0.442678269775446};

        for(int i = 1; i <= N; i++) {
            theta[i] = 0.5;
        }

        System.out.println("【Init】本次拍卖初始化信息");
        System.out.printf("【Init】Mu (%d * %d)\n", M, N);
        MatrixUtils.show(mu[t], M, N);
        System.out.printf("【Init】Lambda (%d * %d)\n", 1, N);
        MatrixUtils.show(lambda[t], N);
        System.out.printf("【Init】Theta (%d * %d)\n", 1, N);
        MatrixUtils.show(theta, N);

    }
    // taskList = [task1, task2]
    // task1 : P11， Q11， 1， 1
    // task2: P24, Q42, 2, 4

    /**
     * PRSUj (xj ) 表示 RSU j 为市场运营商从 EV 获取的数据（向量 xj ）支付的费用。
     */
    private double[][] getPRsuJ(double[][][] u) {
        return u[t];
    }

    public double[][] getPRsuJ() {
        return getPRsuJ(u);
    }

    public double[][] getQEvI() {
        return getQEvI(mu, lambda, v);
    }

    /**
     * QEVi (yi) 表示的 EV i 的得到奖励
     */
    private double[][] getQEvI(double[][][] mu, double[][] lambda, double[][][] v) {
        double[][] res = new double[N + 1][M + 1];
        for(int m = 1; m <= M; m++) {
            for(int n = 1; n <= N; n++) {
                res[n][m] = Math.pow(mu[t][m][n] - lambda[t][n], 2) / v[t][n][m];
            }
        }
        return res;
    }

    public double[][] getX() {
        return x[t];
    }

    public double[][] getY() {
        return y[t];
    }

    /**
     * 求第t轮和, 形成一列行和
     */
    private void getSum(double[][][] matrix, double[][] temp) {
        if(temp[t].length != N + 1) {
            return;
        }

        for(int i = 1; i <= N; i++) {
            double s = 0;
            for(int j = 1; j <= M; j++) {
                s += matrix[t][i][j];
            }
            temp[t][i] = s;
        }
        System.out.printf("【Round %d】Temp\n", t);
        MatrixUtils.show(temp[t], N);
    }

    /**
     * 求第t轮矩阵所有元素的和
     */
    private double getS(double[][][] matrix) {
        double[][] r = matrix[t];
        double ans = 0;
        for(double[] rr : r) {
            for(double x : rr) {
                ans += x;
            }
        }
        return ans;
    }

    /**
     * 根据u, v, x, y 更新 新的X 和 Y矩阵
     */
    private void calXY(double[][][] u, double[][][] v, double[][][] x, double[][][] y) {
        for(int m = 1; m <= M; m++) {
            for(int n = 1; n <= N; n++) {
                x[t][m][n] = u[t][m][n] / mu[t - 1][m][n];
                y[t][n][m] = (mu[t - 1][m][n] - lambda[t - 1][n]) / v[t][n][m];
            }
        }
        System.out.printf("【Round %d】X\n", t);
        MatrixUtils.show(x[t], M, N);
        System.out.printf("【Round %d】Y\n", t);
        MatrixUtils.show(y[t], N, M);
    }

    /**
     * 更新lambda 和 mu矩阵
     */
    private void updateParams(double[][] temp, double[][][] x, double[][][] y) {
        for(int m = 1; m <= M; m++) {
            for(int n = 1; n <= N; n++) {
                lambda[t][n] = Math.max(lambda[t - 1][n] + s * (temp[t][n] - 1), 0);
                //lambda[t][n] = Math.max(lambda[t - 1][n] - s, 0);
                mu[t][m][n] = Math.max(mu[t - 1][m][n] + s * (x[t][m][n] - y[t][n][m]), 0);
            }
        }
        System.out.printf("【Round %d】Lambda\n", t);
        MatrixUtils.show(lambda[t], N);
        System.out.printf("【Round %d】Mu\n", t);
        MatrixUtils.show(mu[t], M, N);
    }

    /**
     * 判断是否达到收敛
     */
    private boolean checkRes(double[][][] u, double[][][] v) {
        double[][] judge = MatrixUtils.zeros(M, N);
        for(int m = 1; m <= M; m++) {
            for(int n = 1; n <= N; n++) {
                if(Math.abs((u[t][m][n] - u[t - 1][m][n]) / u[t - 1][m][n]) < EP1 &&
                        Math.abs((v[t][n][m] - v[t - 1][n][m]) / v[t - 1][n][m]) < EP2) {
                    judge[m - 1][n - 1] = 1;
                }

            }
        }
        return MatrixUtils.equal(judge, MatrixUtils.ones(M, N));
    }

    /**
     * 计算u矩阵(已测试）
     */
    private void calU(double[][][] u) {
        double[] bid1 = new double[LEN + 1];
        for(int i = 1; i <= LEN; i++) {
            bid1[i] = i * 0.1 * 1e-3;
        }
        double[] u1 = new double[LEN + 1];
        for(int m = 1; m <= M; m++) {
            for(int n = 1; n <= N; n++) {
                double mmu = mu[t - 1][m][n];
                for(int i = 1; i <= LEN; i++) {
                    u1[i] = Math.log(1 + (bid1[i] / mmu)) - bid1[i];
                }
                double[] res = getIdxAndVal(u1);
                utility1[m][n] = res[0];
                optbid1[m][n] = res[1];
                u[t][m][n] = 0 + (optbid1[m][n] * 0.0001);
            }
        }
        System.out.printf("【Round %d】U\n", t);
        MatrixUtils.show(u[t], M, N);
    }

    /**
     * 计算v矩阵
     */
    private void calV(double[][][] v) {
        double[] bid2 = new double[LEN + 1];
        for(int i = 1; i <= LEN; i++) {
            bid2[i] = i * 0.1 * 1e-3;
        }
        double[] u2 = new double[LEN + 1];
        for(int m = 1; m <= M; m++) {
            for(int n = 1; n <= N; n++) {
                double mmu = mu[t - 1][m][n];
                double lam = lambda[t - 1][n];
                for(int i = 1; i <= LEN; i++) {
                    u2[i] = (Math.pow(mmu - lam, 2)) / bid2[i] - (Math.exp(theta[n] * (mmu - lam) / bid2[i]) - 1);
                }
                double[] res = getIdxAndVal(u2);
                utility2[n][m] = res[0];
                optbid2[n][m] = res[1];
                v[t][n][m] = 0 + (optbid2[n][m] * 0.0001);
            }
        }
        System.out.printf("【Round %d】V\n", t);
        MatrixUtils.show(v[t], N, M);
    }

    public int getT() {
        return t;
    }


    /**
     * 得到一维数组的最大值的索引pos 和 最大值val
     * res[0] = val, res[1] = pos
     */
    private double[] getIdxAndVal(double[] arr) {
        int n = arr.length;
        double mx, pos;
        if(arr[1] > arr[n - 1]) {
            mx = arr[1];
            pos = 1;
        } else {
            mx = arr[n - 1];
            pos = n - 1;
        }


        for(int i = 2; i < n - 1; i++) {
            if(arr[i] > mx) {
                mx = arr[i];
                pos = i;
            }
        }
        return new double[]{mx, pos};
    }


    public static void main(String[] args) {
        Market market = new Market(2, 2);
        market.IDA();



    }

}
